Azure 上の既存リソースを Terraform 管理へ移行する「Azure Export for Terraform」を試してみる

Azure 上の既存リソースを Terraform 管理へ移行する「Azure Export for Terraform」を試してみる

Clock Icon2025.01.03

いわさです。

先日 Azure リソースを Terraform を使って構築する機会がありました。
私は、普段 AWS では CloudFormation を使うことが多くて Terraform はあまり使うことが無かったのですが、Azure だと ARM/Bicep か Terraform での管理になると思います。

そんな中、Azure + Terraform について調べているうちに、Azure 上で構築済みのリソースを Terraform のコード & Stateファイルへエクスポートするツールが提供されていることを知りました。
Azure + Terraform を学ぶきっかけになるかなと思い、今回こちらのツールを使ってみました。

インストール方法

Apple M1 Max (macOS Sonoma) 環境へインストールを行います。前提として Terraform は既にインストール済みです。
次の公式ドキュメントに従って Homebrew 経由でインストールします。

https://learn.microsoft.com/ja-jp/azure/developer/terraform/azure-export-for-terraform/export-terraform-overview

本日時点の最新版、v0.15.0 がインストールされました。

% brew install aztfexport
==> Downloading https://ghcr.io/v2/homebrew/core/aztfexport/manifests/0.15.0
################################################################################################################################################################## 100.0%
==> Fetching aztfexport
==> Downloading https://ghcr.io/v2/homebrew/core/aztfexport/blobs/sha256:209bceab0e2b561838c6885d68d0e6c5df55b23c3b22a88a3b114763414d9d0c
################################################################################################################################################################## 100.0%
==> Pouring aztfexport--0.15.0.arm64_sonoma.bottle.tar.gz
🍺  /opt/homebrew/Cellar/aztfexport/0.15.0: 6 files, 77.0MB
==> Running `brew cleanup aztfexport`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

% aztfexport --version   
aztfexport version v0.15.0(f1f6cce)

Azure リソースをエクスポートしてみる

Azure ポータル上から手動でリソースを作成し、エクスポートしてみます。
公式ドキュメントでは VNET + 仮想マシンを使った方法が紹介されていました。私は普段よく使う App Service で行ってみようかな。
次のように新規リソースグループに App Service と App Service Plan を作成しました。

00A4A79C-F560-4BCE-95D7-F4A26BD0A9DC.png

で、このツールはリソースグループ名かリソースIDを指定したエクスポートが基本的な使い方となります。
他にも Azure Resource Graph クエリを使ってリソースを指定したエクスポートも可能です。

% aztfexport resource-group hoge0103azt

The output directory is not empty. Please choose one of actions below:

* Press "Y" to proceed that will likely pollute the existing files and cause errors
* Press "N" to append new files and add to the existing state instead
* Press other keys to quit

> Y

aztfexportコマンドを実行すると、provider.tf や terraform.tf ファイルの初期化がまず行われます。

4C202215-864B-4455-9735-22DE4A7BA92A.png

その後、コマンド実行時に指定したリソースがリストアップされます。
対話形式になっており、リソース名の変更も可能です。こちらで良いか確認した後wキーを押します。

A0FF49E5-C001-4241-A0A5-4E334B03FDC8.png

その後 main.tf や terrraform.tfstate の作成が行われます。
ここまでの過程で、内部では AzureRM Terraform Type Finder (aztft) という指定したリソース ID に基づいて ARM Terraform プロバイダーのリソースタイプを紹介するツールが実行されています。
その後terraform importで tfstate ファイルを作成し、tfaddで tfstate ファイルから実行されます。
tfaddではリソース間の依存関係は生成されないので、注意が必要です。

60C6DE03-C514-44A1-BC9A-B2EB6B113D0B.png

実行後数分待つとファイルが一式作成されています。

62A56FA8-C54E-46D1-B616-0823CEE09F68.png

main.tfはこんな感じでした

main.tf
resource "azurerm_resource_group" "res-0" {
  location = "japaneast"
  name     = "hoge0103azt"
}
resource "azurerm_service_plan" "res-1" {
  location            = "eastus"
  name                = "hoge0103appplan"
  os_type             = "Linux"
  resource_group_name = "hoge0103azt"
  sku_name            = "B1"
  depends_on = [
    azurerm_resource_group.res-0,
  ]
}
resource "azurerm_linux_web_app" "res-2" {
  app_settings = {
    APPLICATIONINSIGHTS_CONNECTION_STRING      = "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=6334c389-5ab4-4d6a-859e-129c1acf1dc9"
    ApplicationInsightsAgent_EXTENSION_VERSION = "~3"
    XDT_MicrosoftApplicationInsights_Mode      = "Recommended"
  }
  ftp_publish_basic_authentication_enabled       = false
  https_only                                     = true
  location                                       = "eastus"
  name                                           = "hoge0103app"
  resource_group_name                            = "hoge0103azt"
  service_plan_id                                = "/subscriptions/a50aeedb-979c-428f-8b2d-28974d5e3d3b/resourceGroups/hoge0103azt/providers/Microsoft.Web/serverFarms/hoge0103appplan"
  webdeploy_publish_basic_authentication_enabled = false
  site_config {
    always_on                         = false
    ftps_state                        = "FtpsOnly"
    ip_restriction_default_action     = ""
    scm_ip_restriction_default_action = ""
  }
  depends_on = [
    azurerm_service_plan.res-1,
  ]
}
resource "azurerm_app_service_custom_hostname_binding" "res-6" {
  app_service_name    = "hoge0103app"
  hostname            = "hoge0103app-h6faf2e8d7gkg5b5.eastus-01.azurewebsites.net"
  resource_group_name = "hoge0103azt"
  depends_on = [
    azurerm_linux_web_app.res-2,
  ]
}
resource "azurerm_application_insights" "res-7" {
  application_type    = "web"
  location            = "eastus"
  name                = "hoge0103app"
  resource_group_name = "hoge0103azt"
  sampling_percentage = 0
  workspace_id        = "/subscriptions/a50aeedb-979c-428f-8b2d-28974d5e3d3b/resourceGroups/DefaultResourceGroup-EUS/providers/Microsoft.OperationalInsights/workspaces/DefaultWorkspace-a50aeedb-979c-428f-8b2d-28974d5e3d3b-EUS"
  depends_on = [
    azurerm_resource_group.res-0,
  ]
}

実行計画の確認

terraform planで実行計画を確認してみたのですがエラーが発生しました。
以下 2 つのプロパティで許容されていない値が設定されているようです。むむ。

% terraform init --upgrade
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "3.99.0"...
- Using previously-installed hashicorp/azurerm v3.99.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
% terraform plan
╷
│ Error: expected site_config.0.ip_restriction_default_action to be one of ["Allow" "Deny"], got 
│ 
│   with azurerm_linux_web_app.res-2,
│   on main.tf line 31, in resource "azurerm_linux_web_app" "res-2":31:     ip_restriction_default_action     = ""
│ 
╵
╷
│ Error: expected site_config.0.scm_ip_restriction_default_action to be one of ["Allow" "Deny"], got 
│ 
│   with azurerm_linux_web_app.res-2,
│   on main.tf line 32, in resource "azurerm_linux_web_app" "res-2":32:     scm_ip_restriction_default_action = ""

tfstateファイル上はこんな感じで出力されていました。

terraform.tfstate
:
                "health_check_eviction_time_in_min": 0,
                "health_check_path": "",
                "http2_enabled": false,
                "ip_restriction": [],
                "ip_restriction_default_action": "",
                "linux_fx_version": "DOTNETCORE|9.0",
                "load_balancing_mode": "LeastRequests",
                "local_mysql_enabled": false,
                "managed_pipeline_mode": "Integrated",
                "minimum_tls_version": "1.2",
                "remote_debugging_enabled": false,
                "remote_debugging_version": "",
                "scm_ip_restriction": [],
                "scm_ip_restriction_default_action": "",
                "scm_minimum_tls_version": "1.2",
                "scm_type": "None",
                "scm_use_main_ip_restriction": false,
:

Azure リソースの公式ドキュメントを確認してみましたが、どちらも Optional の値ですね。デフォルトの Allow なので設定なしで出力してくれても良さそうですが。

(Optional) The Default action for traffic that does not match any scm_ip_restriction rule. possible values include Allow and Deny. Defaults to Allow

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app

こちらは、Azure ポータルの構成画面だとこのあたりの IP アドレス制限を指しているようです。

448B63A3-FDA2-4685-90CB-E7DD4C6E3B42.png

これは App Service 作成時に指定出来るオプションなのですが、デフォルトの制限なしのパブリックアクセスを設定していました。

4555748C-6C82-4FBA-AADB-0199E080705A.png

手動でパラメータを追加したり削除すると当然ですが差分が発生します。

% terraform plan
azurerm_resource_group.res-0: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt]
azurerm_service_plan.res-1: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt/providers/Microsoft.Web/serverFarms/hoge0103appplan]
azurerm_application_insights.res-7: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt/providers/Microsoft.Insights/components/hoge0103app]
azurerm_linux_web_app.res-2: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt/providers/Microsoft.Web/sites/hoge0103app]
azurerm_app_service_custom_hostname_binding.res-6: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt/providers/Microsoft.Web/sites/hoge0103app/hostNameBindings/hoge0103app-h6faf2e8d7gkg5b5.eastus-01.azurewebsites.net]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_linux_web_app.res-2 will be updated in-place
  ~ resource "azurerm_linux_web_app" "res-2" {
        id                                             = "/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103azt/providers/Microsoft.Web/sites/hoge0103app"
        name                                           = "hoge0103app"
        tags                                           = {}
        # (25 unchanged attributes hidden)

      ~ site_config {
          + ip_restriction_default_action                 = "Allow"
          + scm_ip_restriction_default_action             = "Allow"
            # (27 unchanged attributes hidden)

            # (1 unchanged block hidden)
        }
    }

Plan: 0 to add, 1 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

デフォルトでいくつかそういった値の出力がされる場合があるみたいなので覚えておきましょう。
ちなみに上記パラメータについてですが、一度 Azure ポータル上で制限値を更新してやることで許容される値で出力されるようになりました。(全パブリック許可でも OK)

18FBE007-B2DE-4101-889C-A84941370F3D.png

以下は上記のように Access Restrictions を値そのままで更新した直後の Plan の様子です。

% terraform plan
azurerm_resource_group.res-0: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103tf2]
azurerm_application_insights.res-8: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103tf2/providers/Microsoft.Insights/components/hoge0103app2]
azurerm_service_plan.res-1: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103tf2/providers/Microsoft.Web/serverFarms/hoge0103plan2]
azurerm_windows_web_app.res-2: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103tf2/providers/Microsoft.Web/sites/hoge0103app2]
azurerm_app_service_custom_hostname_binding.res-6: Refreshing state... [id=/subscriptions/11111111-2222-3333-4444-555555555555/resourceGroups/hoge0103tf2/providers/Microsoft.Web/sites/hoge0103app2/hostNameBindings/hoge0103app2-hpfphqe2ddh4hfhs.eastus-01.azurewebsites.net]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

さいごに

本日は Azure 上の既存リソースを Terraform 管理へ移行する「Azure Export for Terraform」を試してみました。

依存関係の制限事項などもありますので、アウトラインの作成の程度で使うのが良さそうです。
それでも私のような慣れていない人間からするとゼロベースで作成するよりはきっと速いので活用していこうかな。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.